package gl.texture;

import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GLCapabilities;

import java.nio.ByteBuffer;

import static org.lwjgl.opengl.GL42C.*;
import static org.lwjgl.opengl.GL44C.GL_DYNAMIC_STORAGE_BIT;
import static org.lwjgl.opengl.GL44C.glBufferStorage;

public class TextureAtlas2D extends TextureAtlas<TextureAtlas2D.UV2D> {

    public static Builder builder(int width, int height, int maxWidth, int maxHeight) {
        int id = glGenTextures();
        glBindTexture(GL_TEXTURE_2D, id);
        glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA4, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        if (glGetTexLevelParameteri(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) <= 0) {
            throw new RuntimeException("Can't create texture with size " + width + " X " + height);
        }
        GLCapabilities cap = GL.getCapabilities();
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA4, width, height);
        return new Builder(id, width, height, maxWidth, maxHeight);
    }

    protected TextureAtlas2D(int textureId, int width, int height) {
        super(GL11.GL_TEXTURE_2D, GL_TEXTURE_BINDING_2D, textureId, width, height);
    }

    @Override
    public void remove() {
        super.remove();
    }

    public static class Builder {

        private final TextureAtlas2D atlas;
        private final int buffer;

        Builder(int id, int width, int height, int maxWidth, int maxHeight) {
            atlas = new TextureAtlas2D(id, width, height);
            atlas.bind();
            buffer = glGenBuffers();
            glBufferStorage(GL_PIXEL_UNPACK_BUFFER, maxWidth * maxHeight * 4L, GL_DYNAMIC_STORAGE_BIT);
        }

        public Builder add(String name, int width, int height, int posX, int posY, ByteBuffer image) {
            return add(name, width, height, posX, posY, image, 0);
        }

        public Builder add(String name, int width, int height, int posX, int posY, ByteBuffer image, int level) {
            subTex(width, height, posX, posY, image, level);
            atlas.put(name, new UV2D(width, height, posX, posY, atlas));
            return this;
        }

        protected void subTex(int width, int height, int posX, int posY, ByteBuffer image, int level) {
            glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, image);
            glTexSubImage2D(GL_TEXTURE_2D, level, posX, posY, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        }

        public TextureAtlas2D build() {
            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
            glDeleteBuffers(buffer);
            return atlas;
        }
    }

    public static class UV2D {
        public final float minU, minV, maxU, maxV;
        public final int width, height, startU, endU, startV, endV;
        public final TextureAtlas2D atlas;

        public UV2D(int width, int height, int startU, int startV, TextureAtlas2D atlas) {
            this.width = width;
            this.height = height;
            this.startU = startU;
            this.startV = startV;
            this.atlas = atlas;

            float totalW = atlas.width;
            float totalH = atlas.height;

            endU = startU + width;
            endV = startV + height;
            minU = startU / totalW;
            minV = startV / totalH;
            maxU = endU / totalW;
            maxV = endV / totalH;
        }

        public float getMinU() {
            return minU;
        }

        public float getMinV() {
            return minV;
        }

        public float getMaxU() {
            return maxU;
        }

        public float getMaxV() {
            return maxV;
        }

        public int getWidth() {
            return width;
        }

        public int getHeight() {
            return height;
        }

        public int getStartU() {
            return startU;
        }

        public int getEndU() {
            return endU;
        }

        public int getStartV() {
            return startV;
        }

        public int getEndV() {
            return endV;
        }

        public TextureAtlas2D getAtlas() {
            return atlas;
        }
    }
}
